package main // 如果想使用 func main(){} ,这里就必须导入 main 包,否则无法执行测试

import (
	"errors"
	"fmt"
	"strconv"
	"time"
)

/*
项目整体架构说明:
参考资料:https://www.cnblogs.com/qfDavie/p/10637652.html

config：项目配置文件及读取配置文件的相关功能

controller：控制器目录、项目各个模块的控制器及业务逻辑处理的所在目录

datasource：实现mysql连接和操作、封装操作mysql数据库的目录。

model：数据实体目录，主要是项目中各业务模块的实体对象的定义

service：服务层目录。用于各个模块的基础功能接口定义及实现，是各个模块的数据层。

static：配置项目的静态资源目录。

util：提供通用的方法封装。

main.go：项目程序主入口

config.json：项目配置文件。


*/
func main() {
	/*
		此文件是Go的基本语法操作,涉及到:
		变量的声明, 赋值, 打印输出, 基本数据类型的转换, 时间与格式的使用和转换, 函数, 数组
	*/

	// 1.变量的声明使用,输出打印:
	//declareVariables()
	// 2.基本数据类型的相互转换:
	//exchangeBaseType()
	// 3.时间类型的格式设置
	//timeFormat()
	// 4.数组的使用
	//array()
	// 5. 结构体的创建和实例化
	structTest()
	// 6. 自定义错误信息
	//errorTest()
	// 7. 异常的处理
	exception()
}

func declareVariables() {
	var a = 20  /* 声明实际变量 */
	var ip *int /* 声明指针变量 */
	fmt.Printf("%v %x %d\n", ip, ip, ip)
	//fmt.Println(*ip)
	fmt.Println(&ip)
	ip = &a /* 指针变量的存储地址 */
	fmt.Printf("a 变量的地址是: %x\n", &a)
	/* 指针变量的存储地址 */
	fmt.Printf("ip 变量储存的指针地址: %x\n", ip)
	/* 使用指针访问值 */
	fmt.Printf("*ip 变量的值: %d\n", *ip)
}

func exchangeBaseType() {
	// string到int
	intTest, err := strconv.Atoi("string")
	// string到int64
	int64Test, err := strconv.ParseInt("string", 10, 64)
	// int 或 int64 到string (Itoa  和  FormatInt 是完全一样的! )
	string1 := strconv.Itoa(123)
	string2 := strconv.FormatInt(int64Test, 10)
	if err == nil {
		fmt.Println(intTest, int64Test, string1, string2)
	}
}

func timeFormat() {
	var timeNow = time.Now().Unix()
	var startDate = time.Unix(timeNow, 0).Format("2006-01-02 15:04:05")
	timeString := strconv.FormatInt(timeNow, 10)
	fmt.Println("现在时刻是:"+startDate+"\n 这是time.Now().Unix()的值: ", timeNow, "\n 这是字符串类型的值:"+timeString)
}

func array() {
	var abc = [...]int{1, 2, 3, 4, 5, 6}
	var array [10]*int
	fmt.Println(abc[0], len(abc), array)
}

func structTest() {
	// 默认情况下，Go 语言使用的是值传递，即使是对象,也是复制的副本, 即在调用过程中形参的变化不会影响到实际参数。
	// 如果想类似java改变实参的数据,要使用到指针这个概念. 但是和java一样,并没有引用传递这个概念.
	type User struct {
		//每个结构体名可以对应表名, 结构体成员变量对应数据库column名,必须大写开头
		Id       int
		Account  string
		Nickname string
	}

	// 实例化对象,下面的两种方式效果一样,可以只实例化一部分属性,但是注意基本数据类型默认为0
	user := User{Id: 1, Account: "8858588", Nickname: "阿金测试"}
	user1 := new(User) // 使用new创建一个user对象，同时获得user的指针
	user1.Id = 1
	user1.Account = "8858588"
	user1.Nickname = "阿金测试"

	// userCopy 是 user 对象的复制品,和java不同,它们互不影响.
	userCopy := user
	fmt.Println("user和userCopy对象的值:", user, userCopy)
	// userPointer 是指针对象,存放了 user 对象的地址值,也就是 &user, 此时,userPointer 类似于java,复制了栈地址,但是引用的是
	// 同一个堆地址, 所以 userPointer 的动作会对 user 的数据产生影响, 但是不影响 userCopy
	userPointer := &user
	userPointer.Id = 2
	userPointer.Nickname = "修改后的名字"
	fmt.Println("userPointer的值:", userPointer)
	fmt.Println("修改userPointer之后user和userCopy对象的值:", user, userCopy)
	// 获取指针对象的值:
	fmt.Println(*userPointer)

	/*
		new和其他语言中的同名函数一样：new(t)分配了零值填充的T类型的内存空间，并且返回其地址，即一个*t类型的值。
		它并不初始化内存，只是将其置零。*t指向的内容的值为零（zero value）。注意并不是指针为零。
		make(t, args)与new(t)的功能区别是，make只能创建slice、map和channel，,并且返回一个初始化的(而不是置零)，类型为t的值
		（而不是*t）。内建函数 make 用来为 slice，map 或 chan 类型分配内存和初始化一个对象(注意：只能用在这三种类型上)，跟 new
		类似，第一个参数也是一个类型而不是一个值，跟 new 不同的是，make 返回类型的引用而不是指针，而返回值也依赖于具体传入的类型，
	*/
}

func errorTest() {
	errSqrt := Sqrt(11)
	fmt.Println(errSqrt)
}

func Sqrt(f float64) error {
	if f < 0 {
		return errors.New("math: square root of negative number")
	} else {
		return nil
	}
}

func exception() {
	// 在 JAVA 中 try, catch, final 语句可以捕获并处理异常, Go 中则用 panic 充当throw exception 操作,
	// defer 语句块充当final 操作, recovery()只能用在defer语句中, 用于捕获当前函数中可能出现的异常
	defer func() { // 类似final语句, 但是必须要首先声明defer，否则不能捕获到panic异常
		fmt.Println("c")                  //
		if err := recover(); err != nil { // 捕获异常
			fmt.Println(err) // 这里的err其实就是panic传入的内容，55
		}
		fmt.Println("d")
	}()

	testFunc()
}
func testFunc() {
	fmt.Println("a")
	panic(55) // 抛出异常, 下面的代码就不会执行
	fmt.Println("b")
	fmt.Println("f")
}
